/*
 *  Copyright 2005 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.util.core;

import com.blandware.atleap.common.Constants;
import com.blandware.atleap.service.core.StatisticsManager;
import com.blandware.atleap.service.core.util.RawData;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>Holds raw statictics data. Passes it to StatisticsManager when overfilled
 * or when requested.</p>
 * <p>One instance per http-session is maintained.</p>
 * <p><a href="RawDataCollector.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.4 $ $Date: 2006/05/30 09:22:03 $
 */
public class RawDataCollector implements Serializable {

	/**
	 * Commons Logging instance.
	 */
	protected transient final Log log = LogFactory.getLog(RawDataCollector.class);

    /**
     * List of items describing raw data
     */
    protected List rawDataList = new ArrayList();

    /**
     * When number of collected raw data items reaches this threshold, it's
     * flushed
     */
    protected int flushThreshold = 10;

    /**
     * Number of session
     */
    protected Integer sessionNumber = null;

    /**
     * Number of visitor
     */
    protected Integer visitorNumber = null;

    /**
     * Whether this collector data has not yet ever been flushed (so it
     * contains first session visit)
     */
    protected boolean isFresh = true;

    /**
     * Constructs an instance of RawDataCollector
     */
    private RawDataCollector(Integer visitorNumber,
                             StatisticsUtil statisticsUtil,
                             Integer flushThreshold) {
        if (flushThreshold == null || flushThreshold.intValue() <= 1) {
            this.flushThreshold = Constants.DEFAULT_FLUSH_THRESHOLD;
        } else {
            this.flushThreshold = flushThreshold.intValue();
        }
        synchronized (rawDataList) {
            rawDataList = new ArrayList(this.flushThreshold);
            isFresh = true;
        }
        sessionNumber = null;
        if (visitorNumber != null
                && statisticsUtil.visitorNumberIsCorrect(visitorNumber)) {
            this.visitorNumber = visitorNumber;
        } else {
            this.visitorNumber = statisticsUtil.getNextFreeVisitorNumber();
        }
    }

    /**
     * Returns instance of RawDataCollector assigned to a given session.
     *
     * @param session session for which to obtain instance
     * @param visitorNumber assumed visitor number. If some visitor number is
     * already known to collector, this is ignored. Else, if there's a visitor
     * with such number, it's used; is there's no visitor with such number or
     * no number is supplied (null), new number is generated.
     * @param statisticsUtil util used to initialize some values
     * @return class instance
     */
    public static synchronized RawDataCollector getInstance(HttpSession session,
                                               Integer visitorNumber,
                                               StatisticsUtil statisticsUtil,
                                               Integer flushThreshold) {
        String key = WebappConstants.RAW_DATA_HOLDER_KEY;
        RawDataCollector instance = (RawDataCollector) session.getAttribute(key);
        if (instance == null) {
            instance = new RawDataCollector(visitorNumber, statisticsUtil,
                                            flushThreshold);
            session.setAttribute(key, instance);
        }
        return instance;
    }

    /**
     * Adds a raw data item to collector. If collector is overfilled, all
     * collected data is flushed.
     * Session number is assigned during adding.
     *
     * @param data raw data to add
     * @param statisticsManager manager used to flush data if collector becomes
     * overfilled
     */
    public void addData(RawData data, StatisticsManager statisticsManager) {
        data.setVisitorNumber(visitorNumber);
        synchronized (rawDataList) {
            rawDataList.add(data);
        }
        if (rawDataList.size() >= flushThreshold) {
            flushData(statisticsManager, false);
        }
    }

    /**
     * Passes all collected data to StatisticsManager for processing.
     *
     * @param statisticsManager manager to which to pass data
     * @param sessionIsClosing whether session is closing
     */
    public void flushData(StatisticsManager statisticsManager,
                          boolean sessionIsClosing) {
        synchronized (rawDataList) {
            int listSize = rawDataList.size();
            // Clearing list BEFORE passing it to a manager, because otherwise
            // if some error occure in manager, list will remain; it seems
            // better to lose some statistics than to run out of memory
            List listToProcess;
            if (sessionIsClosing) {
                listToProcess = rawDataList;
                rawDataList = new ArrayList(flushThreshold);
            } else {
                // looking for last visit to NON-RESOURCE
                int index;
                for (index = listSize - 1; index >= 0; index--) {
                    RawData data = (RawData) rawDataList.get(index);
                    if (data.isNotResource()) {
                        break;
                    }
                }
                if (index < 0) {
                    // only resources are being flushed
                    listToProcess = rawDataList;
                    rawDataList = new ArrayList(flushThreshold);
                } else {
                    RawData data = (RawData) rawDataList.get(index);
                    rawDataList.remove(index);
                    listToProcess = rawDataList;
                    rawDataList = new ArrayList(flushThreshold);
                    rawDataList.add(data);
                }
            }

            // figure out whether there's a NON-RESOURCE being flushed
            boolean nonResourceIsBeingFlushed = false;
            if (isFresh) {
                for (int i = 0; i < listToProcess.size(); i++) {
                    RawData data = (RawData) listToProcess.get(i);
                    if (data.isNotResource()) {
                        nonResourceIsBeingFlushed = true;
                        break;
                    }
                }
            }

            boolean containsEntryPoint = isFresh && nonResourceIsBeingFlushed;
            if (containsEntryPoint) {
                isFresh = false;
            }
            sessionNumber = statisticsManager.processRawData(listToProcess,
                                                             containsEntryPoint,
                                                             sessionIsClosing,
                                                             sessionNumber);
        }
    }

    /**
     * Returns visitor number.
     *
     * @return visitor number
     */
    public Integer getVisitorNumber() {
        return visitorNumber;
    }
}
